msg_tool\scripts\bgi\image/
img.rs1use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::img::*;
6use anyhow::Result;
7
8fn try_parse(buf: &[u8]) -> Result<u8> {
9 let mut reader = MemReaderRef::new(buf);
10 let width = reader.read_u16()?;
11 let height = reader.read_u16()?;
12 let bpp = reader.read_u16()?;
13 let _flag = reader.read_u16()?;
14 let padding = reader.read_u64()?;
15 if padding != 0 {
16 return Err(anyhow::anyhow!("Invalid padding: {}", padding));
17 }
18 if width == 0 || height == 0 {
19 return Err(anyhow::anyhow!("Invalid dimensions: {}x{}", width, height));
20 }
21 if width > 4096 || height > 4096 {
22 return Err(anyhow::anyhow!(
23 "Dimensions too large: {}x{}",
24 width,
25 height
26 ));
27 }
28 if bpp != 8 && bpp != 24 && bpp != 32 {
29 return Err(anyhow::anyhow!("Unsupported BPP: {}", bpp));
30 }
31 Ok(1)
32}
33
34#[derive(Debug)]
35pub struct BgiImageBuilder {}
37
38impl BgiImageBuilder {
39 pub const fn new() -> Self {
41 BgiImageBuilder {}
42 }
43}
44
45impl ScriptBuilder for BgiImageBuilder {
46 fn default_encoding(&self) -> Encoding {
47 Encoding::Cp932
48 }
49
50 fn build_script(
51 &self,
52 data: Vec<u8>,
53 _filename: &str,
54 _encoding: Encoding,
55 _archive_encoding: Encoding,
56 config: &ExtraConfig,
57 _archive: Option<&Box<dyn Script>>,
58 ) -> Result<Box<dyn Script>> {
59 Ok(Box::new(BgiImage::new(data, config)?))
60 }
61
62 fn extensions(&self) -> &'static [&'static str] {
63 &[]
64 }
65
66 fn script_type(&self) -> &'static ScriptType {
67 &ScriptType::BGIImg
68 }
69
70 fn is_image(&self) -> bool {
71 true
72 }
73
74 fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
75 if buf_len >= 0x10 {
76 return try_parse(&buf[0..0x10]).ok();
77 }
78 None
79 }
80
81 fn can_create_image_file(&self) -> bool {
82 true
83 }
84
85 fn create_image_file<'a>(
86 &'a self,
87 data: ImageData,
88 writer: Box<dyn WriteSeek + 'a>,
89 options: &ExtraConfig,
90 ) -> Result<()> {
91 create_image(data, writer, options.bgi_img_scramble.unwrap_or(false))
92 }
93}
94
95#[derive(Debug)]
96pub struct BgiImage {
98 data: MemReader,
99 width: u32,
100 height: u32,
101 color_type: ImageColorType,
102 is_scrambled: bool,
103 opt_is_scrambled: Option<bool>,
104}
105
106fn create_image<'a>(
107 mut data: ImageData,
108 mut writer: Box<dyn WriteSeek + 'a>,
109 scrambled: bool,
110) -> Result<()> {
111 writer.write_u16(data.width as u16)?;
112 writer.write_u16(data.height as u16)?;
113 if data.depth != 8 {
114 return Err(anyhow::anyhow!("Unsupported image depth: {}", data.depth));
115 }
116 match data.color_type {
117 ImageColorType::Bgr => {}
118 ImageColorType::Bgra => {}
119 ImageColorType::Grayscale => {}
120 ImageColorType::Rgb => {
121 convert_rgb_to_bgr(&mut data)?;
122 }
123 ImageColorType::Rgba => {
124 convert_rgba_to_bgra(&mut data)?;
125 }
126 }
127 let bpp = data.color_type.bpp(8);
128 writer.write_u16(bpp)?;
129 let flag = if scrambled { 1 } else { 0 };
130 writer.write_u16(flag)?;
131 writer.write_u64(0)?; let stride = data.width as usize * ((data.color_type.bpp(8) as usize + 7) / 8);
133 let buf_size = stride * data.height as usize;
134 if scrambled {
135 let bpp = data.color_type.bpp(1) as usize;
136 for i in 0..bpp {
137 let mut dst = i;
138 let mut incr = 0u8;
139 let mut h = data.height;
140 while h > 0 {
141 for _ in 0..data.width {
142 writer.write_u8(data.data[dst].wrapping_sub(incr))?;
143 incr = data.data[dst];
144 dst += bpp;
145 }
146 h -= 1;
147 if h == 0 {
148 break;
149 }
150 dst += stride;
151 let mut pos = dst;
152 for _ in 0..data.width {
153 pos -= bpp;
154 writer.write_u8(data.data[pos].wrapping_sub(incr))?;
155 incr = data.data[pos];
156 }
157 h -= 1;
158 }
159 }
160 } else {
161 writer.write_all(&data.data[..buf_size])?;
164 }
165 Ok(())
166}
167
168impl BgiImage {
169 pub fn new(buf: Vec<u8>, config: &ExtraConfig) -> Result<Self> {
174 let mut reader = MemReader::new(buf);
175 let width = reader.read_u16()? as u32;
176 let height = reader.read_u16()? as u32;
177 let bpp = reader.read_u16()?;
178 let color_type = match bpp {
179 8 => ImageColorType::Grayscale,
180 24 => ImageColorType::Bgr,
181 32 => ImageColorType::Bgra,
182 _ => return Err(anyhow::anyhow!("Unsupported BPP: {}", bpp)),
183 };
184 let flag = reader.read_u16()?;
185 let padding = reader.read_u64()?;
186 if padding != 0 {
187 return Err(anyhow::anyhow!("Invalid padding: {}", padding));
188 }
189 let is_scrambled = flag != 0;
190
191 Ok(BgiImage {
192 data: reader,
193 width,
194 height,
195 color_type,
196 is_scrambled,
197 opt_is_scrambled: config.bgi_img_scramble,
198 })
199 }
200}
201
202impl Script for BgiImage {
203 fn default_output_script_type(&self) -> OutputScriptType {
204 OutputScriptType::Json
205 }
206
207 fn default_format_type(&self) -> FormatOptions {
208 FormatOptions::None
209 }
210
211 fn is_image(&self) -> bool {
212 true
213 }
214
215 fn export_image(&self) -> Result<ImageData> {
216 let stride = self.width as usize * ((self.color_type.bpp(8) as usize + 7) / 8);
217 let buf_size = stride * self.height as usize;
218 let mut data = Vec::with_capacity(buf_size);
219 data.resize(buf_size, 0);
220 if self.is_scrambled {
221 let mut reader = self.data.to_ref();
222 reader.pos = 0x10;
223 let bpp = self.color_type.bpp(1) as usize;
224 for i in 0..bpp {
225 let mut dst = i;
226 let mut incr = 0u8;
227 let mut h = self.height;
228 while h > 0 {
229 for _ in 0..self.width {
230 incr = incr.wrapping_add(reader.read_u8()?);
231 data[dst] = incr;
232 dst += bpp;
233 }
234 h -= 1;
235 if h == 0 {
236 break;
237 }
238 dst += stride;
239 let mut pos = dst;
240 for _ in 0..self.width {
241 pos -= bpp;
242 incr = incr.wrapping_add(reader.read_u8()?);
243 data[pos] = incr;
244 }
245 h -= 1;
246 }
247 }
248 } else {
249 self.data.cpeek_exact_at(0x10, &mut data)?;
250 }
251 Ok(ImageData {
252 width: self.width,
253 height: self.height,
254 color_type: self.color_type,
255 depth: 8,
256 data,
257 })
258 }
259
260 fn import_image<'a>(&'a self, data: ImageData, file: Box<dyn WriteSeek + 'a>) -> Result<()> {
261 create_image(
262 data,
263 file,
264 self.opt_is_scrambled.unwrap_or(self.is_scrambled),
265 )
266 }
267}